﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data.SqlClient;
using System.Collections.Specialized;
using System.Net;

namespace Browis.Utils
{
    public enum StorageType : int
    {
        Any = 0,
        FileSystem = 1,
        Database = 2
    }

    public class UnifiedStorageStream : Stream
    {
        /// <summary>
        /// 统一存储文件流系统初始化标志。
        /// </summary>
        private static bool initialized;

        /// <summary>
        /// 统一存储文件流的数据库连接字符串。
        /// </summary>
        private static string connectionString;

        /// <summary>
        /// 统一存储文件流的基本路径。
        /// </summary>
        private static string baseDirectory;

        /// <summary>
        /// 文件名称。
        /// </summary>
        private string fileName;

        /// <summary>
        /// 文件存储类型。
        /// </summary>
        private StorageType storageType;

        /// <summary>
        /// 文件访问权限。
        /// </summary>
        private FileAccess fileAccess;

        /// <summary>
        /// 统一存储文件流的文件系统的文件流。
        /// </summary>
        private FileStream fileStream;

        /// <summary>
        /// 统一存储文件流是否已打开。
        /// </summary>
        private bool isOpen;

        /// <summary>
        /// 统一存储文件流的长度。
        /// </summary>
        private long length;

        /// <summary>
        /// 统一存储文件流的当前指针位置。
        /// </summary>
        private long position;

        /// <summary>
        /// 获取统一存储流是否初始化的标志
        /// </summary>
        public static bool IsInitialized
        {
            get
            {
                return initialized;
            }
        }

        /// <summary>
        /// 获取统一存储流对应的链接串
        /// </summary>
        public static string ConnectionString
        {
            get
            {
                return connectionString;
            }
        }

        /// <summary>
        /// 获取统一存储流对应的根目录
        /// </summary>
        public static string BaseDirectory
        {
            get
            {
                return baseDirectory;
            }
        }
        /// <summary>
        /// 获取一个值，该值指示当前流是否支持读取。
        /// </summary>
        public override bool CanRead
        {
            get { return (fileAccess == FileAccess.ReadWrite || fileAccess == FileAccess.Read) && isOpen; }
        }

        /// <summary>
        /// 获取一个值，该值指示当前流是否支持移动文件指针。
        /// </summary>
        public override bool CanSeek
        {
            get { return isOpen; }
        }

        /// <summary>
        /// 获取一个值，该值指示当前流是否支持写入。
        /// </summary>
        public override bool CanWrite
        {
            get { return (fileAccess == FileAccess.ReadWrite || fileAccess == FileAccess.Write) && isOpen; }
        }

        /// <summary>
        /// 获取文件的名称。
        /// </summary>
        public string FileName
        {
            get { return fileName; }
        }

        /// <summary>
        /// 获取用字节表示的流长度。
        /// </summary>
        public override long Length
        {
            get { return length; }
        }

        /// <summary>
        /// 获取或设置当前流中的位置。
        /// </summary>
        public override long Position
        {
            get { return position; }
            set { Seek(value, SeekOrigin.Begin); }
        }

        public UnifiedStorageStream(string fileName, StorageType storageType, FileMode fileMode, FileAccess fileAccess)
        {
            if (initialized == false)
                throw new Exception("统一存储文件流系统未初始化");

            if(string.IsNullOrEmpty(fileName)) throw new ArgumentException("fileName");
            this.fileName = fileName;

            this.storageType = storageType;
            this.fileAccess = fileAccess;

            if (fileMode == FileMode.Append && fileAccess != FileAccess.Write)
                throw new ArgumentException("FileMode.Append模式打开文件时只能有写访问权限");

            if (fileMode == FileMode.Truncate && fileAccess != FileAccess.Write && fileAccess != FileAccess.ReadWrite)
                throw new ArgumentException("FileMode.Truncate模式打开文件时必需有写访问权限");

            using (SqlConnection cn = new SqlConnection())
            {
                cn.ConnectionString = connectionString;
                cn.Open();

                SqlCommand cmd = cn.CreateCommand();
                cmd.CommandType = System.Data.CommandType.Text;
                cmd.CommandText = "SELECT COUNT(*) FROM browis_FileStorage WHERE FileGUID=?FileGUID";
                cmd.Parameters.Add("FileGUID", System.Data.SqlDbType.NVarChar).Value = Path.GetFileName(fileName);
                int exist = (int)cmd.ExecuteScalar();
                //获取数据库时间
                cmd.CommandText = "SELECT now()";
                DateTime nowTime = (DateTime)cmd.ExecuteScalar();

                if (exist == 0)
                {
                    if (fileMode == FileMode.Open)
                        throw new FileNotFoundException(String.Format("文件{0}不存在", fileName));


                    cmd.CommandText = @"INSERT INTO browis_FileStorage(FileGUID,FileType,StorageType,FileSize,CreateTime,UpdateTime,Path,FileContent) 
                            VALUES(?FileGUID,?FileType,?StorageType,0,?CreateTime,?UpdateTime,?Path,CONVERT('',BINARY))";
                    cmd.Parameters.Clear();
                    cmd.Parameters.Add("FileGUID", System.Data.SqlDbType.NVarChar).Value = Path.GetFileName(fileName);
                    cmd.Parameters.Add("FileType", System.Data.SqlDbType.NVarChar).Value = fileName.IndexOf('.') == -1 ? "" : fileName.Substring(fileName.LastIndexOf('.'));
                    cmd.Parameters.Add("StorageType", System.Data.SqlDbType.Int).Value = (int)storageType;
                    cmd.Parameters.Add("CreateTime", System.Data.SqlDbType.DateTime).Value = nowTime;
                    cmd.Parameters.Add("UpdateTime", System.Data.SqlDbType.DateTime).Value = nowTime;
                    cmd.Parameters.Add("Path", System.Data.SqlDbType.NVarChar);
                    if (storageType == StorageType.FileSystem)
                    {
                        cmd.Parameters["Path"].Value = Path.GetDirectoryName(fileName);
                    }
                    else
                    {
                        cmd.Parameters["Path"].Value = "";
                    }
                    cmd.ExecuteNonQuery();

                    this.length = 0;
                    this.position = 0;
                }
                else
                {
                    if (fileMode == FileMode.CreateNew)
                        throw new IOException(String.Format("文件{0}已存在", fileName));

                    cmd.CommandText = "SELECT StorageType,CreateTime FROM browis_FileStorage WHERE FileGUID=?FileGUID";
                    cmd.Parameters.Clear();
                    cmd.Parameters.Add("FileGUID", System.Data.SqlDbType.NVarChar).Value = Path.GetFileName(fileName);
                    using (SqlDataReader dr = cmd.ExecuteReader())
                    {
                        if (dr.Read())
                        {
                            if (storageType == StorageType.Any)
                                storageType = (StorageType)dr.GetInt32(0);
                            else if (storageType != (StorageType)dr.GetInt32(0))
                                throw new ArgumentException("storageType");
                            nowTime = dr.GetDateTime(1);
                        }
                    }

                    if (fileMode == FileMode.Create || fileMode == FileMode.Truncate)
                    {
                        cmd.CommandText = "UPDATE browis_FileStorage SET FileContent=CONVERT('',BINARY),UpdateTime=now() WHERE FileGUID=?FileGUID";
                        cmd.Parameters.Clear();
                        cmd.Parameters.Add("FileGUID", System.Data.SqlDbType.NVarChar).Value = Path.GetFileName(fileName);
                        cmd.ExecuteNonQuery();

                        this.length = 0;
                        this.position = 0;
                    }
                    else if (storageType == StorageType.Database)
                    {
                        cmd.CommandText = "SELECT DATALENGTH(FileContent) FROM browis_FileStorage WHERE FileGUID=?FileGUID";
                        cmd.Parameters.Clear();
                        cmd.Parameters.Add("FileGUID", System.Data.SqlDbType.NVarChar).Value = fileName;
                        this.length = (long)cmd.ExecuteScalar();
                        if (fileMode == FileMode.Open || fileMode == FileMode.OpenOrCreate)
                            this.position = 0;
                        else
                            this.position = this.Length;
                    }
                }

                if (storageType == StorageType.FileSystem)
                {
                    string dir = Path.Combine(baseDirectory, nowTime.ToString("yyyyMMdd"));
                    if (!Directory.Exists(dir))
                    {
                        Directory.CreateDirectory(dir);
                    }

                    fileStream = new FileStream(Path.Combine(dir, fileName), fileMode, fileAccess);
                    this.position = fileStream.Position;
                    this.length = fileStream.Length;
                }

                this.isOpen = true;
            }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            if (this.isOpen == false)
                throw new ObjectDisposedException(this.ToString());

            switch (origin)
            {
                case SeekOrigin.Begin:
                    this.position = offset;
                    break;
                case SeekOrigin.Current:
                    this.position += offset;
                    break;
                case SeekOrigin.End:
                    this.position = this.length + offset;
                    break;
            }
            if (this.position < 0)
                this.position = 0;

            if (this.position > this.length)
                this.position = this.length;

            return this.position;
        }

        public override void SetLength(long value)
        {
            if (this.isOpen == false)
                throw new ObjectDisposedException("文件流已关闭");

            if (this.CanWrite == false)
                throw new NotSupportedException("文件流未以写模式打开，不支持该操作");

            if (fileStream != null)
            {
                fileStream.SetLength(value);
            }
            else
            {
                using (SqlConnection cn = new SqlConnection())
                {
                    cn.ConnectionString = connectionString;
                    cn.Open();

                    SqlCommand cmd = cn.CreateCommand();
                    cmd.CommandType = System.Data.CommandType.Text;
                    cmd.CommandText = "SELECT DATALENGTH(FileContent) FROM browis_FileStorage WHERE FileGUID=?FileGUID";
                    cmd.Parameters.Add("FileGUID", System.Data.SqlDbType.NVarChar).Value = fileName;
                    long len = (long)cmd.ExecuteScalar();
                    cmd.CommandText = "";

                    if (len < value)
                    {
                        //cmd.CommandText = "DECLARE @l INT;" +
                        //    "SELECT @l=0;" + "while @l<" + value / 1048576 + "BEGIN";
                        while (value - len > 1048576)
                        {
                            cmd.CommandText += String.Format("UPDATE browis_FileStorage SET FileContent.Write(CONVERT(VARBINARY(MAX),REPLICATE(CONVERT(VARBINARY(MAX),0x0,0), 1048576)),{0},1048576) WHERE FileGUID=@FileGUID;", len);
                            len += 1048576;
                        }
                        cmd.CommandText += String.Format("UPDATE browis_FileStorage SET FileContent.Write(CONVERT(VARBINARY(MAX),REPLICATE(CONVERT(VARBINARY(MAX),0x0,0), {1})),{0},{1}) WHERE FileGUID=@FileGUID;", len, value - len);
                    }
                    else
                        cmd.CommandText = String.Format("UPDATE browis_FileStorage SET FileContent=SUBSTRING(FileContent,1,{0}) WHERE FileGUID=@FileGUID", value);

                    cmd.CommandTimeout = 300;
                    cmd.ExecuteNonQuery();
                    cn.Close();
                }
            }
            length = value;
        }

        public override void Flush()
        {
            if (this.isOpen == false)
                throw new ObjectDisposedException("文件流已关闭");

            if (fileStream != null)
                fileStream.Flush();
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            if (this.isOpen == false)
                throw new ObjectDisposedException("文件流已关闭");

            if (this.CanRead == false)
                throw new NotSupportedException("文件流未以读模式打开，不支持读取操作");

            if (buffer == null)
                throw new ArgumentNullException("buffer");

            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset");

            if (count < 0)
                throw new ArgumentOutOfRangeException("count");

            if (buffer.Length < offset + count)
                throw new ArgumentException("缓存区长度过小");

            if (position + count > length)
                count = (int)(length - position);

            if (count == 0)
                return 0;

            if (fileStream != null)
            {
                fileStream.Position = this.position;
                count = fileStream.Read(buffer, offset, count);
            }
            else
            {
                byte[] data = null;
                using (SqlConnection cn = new SqlConnection())
                {
                    cn.ConnectionString = connectionString;
                    cn.Open();

                    SqlCommand cmd = cn.CreateCommand();
                    cmd.CommandType = System.Data.CommandType.Text;
                    cmd.CommandText = String.Format("SELECT SUBSTRING(FileContent,{0},{1}) FROM browis_FileStorage WHERE FileGUID=@FileGUID", position + 1, count);
                    cmd.Parameters.Add("FileGUID", System.Data.SqlDbType.NVarChar).Value = fileName;
                    data = (byte[])cmd.ExecuteScalar();
                    cn.Close();
                }
                System.Buffer.BlockCopy(data, 0, buffer, offset, count);
            }
            this.position += count;

            return count;
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            if (this.isOpen == false)
                throw new ObjectDisposedException("文件流已关闭");

            if (this.CanWrite == false)
                throw new NotSupportedException("文件流未以写模式打开，不支持写入操作");

            if (buffer == null)
                throw new ArgumentNullException("buffer");

            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset");

            if (count < 0)
                throw new ArgumentOutOfRangeException("count");

            if (buffer.Length < offset + count)
                throw new ArgumentException("缓存区长度过小");

            if (count == 0) return;

            using (SqlConnection cn = new SqlConnection())
            {
                cn.ConnectionString = connectionString;
                cn.Open();

                SqlCommand cmd = cn.CreateCommand();
                cmd.CommandType = System.Data.CommandType.Text;

                if (fileStream != null)
                {
                    fileStream.Position = this.position;
                    fileStream.Write(buffer, offset, count);
                    //cmd.CommandText = "UPDATE browis_FileStorage SET UpdateTime=GETDATE() WHERE FileGUID=@FileGUID";
                    //cmd.Parameters.Add("FileGUID", System.Data.SqlDbType.NVarChar).Value = fileName;
                    //cmd.ExecuteNonQuery();
                }
                else
                {
                    cmd.CommandTimeout = 300;
                    cmd.CommandText = String.Format("UPDATE browis_FileStorage SET FileContent.WRITE(@Bytes,{0},{1}) WHERE FileGUID=@FileGUID", position, count);//, fileName);
                    cmd.Parameters.Add("Bytes", System.Data.SqlDbType.VarBinary);
                    cmd.Parameters[0].Value = buffer;
                    cmd.Parameters[0].Offset = offset;
                    cmd.Parameters[0].Size = count;
                    cmd.Parameters.Add("FileGUID", System.Data.SqlDbType.NVarChar).Value = fileName;
                    cmd.ExecuteNonQuery();
                }
                this.position += count;

                if (this.position > this.length)
                    this.length = this.position;
                cn.Close();
            }
        }

        /// <summary>
        /// 初始化统一存储文件流。
        /// </summary>
        public static void Initialize(NameValueCollection config)
        {
            connectionString = config["ConnectionString"];
            baseDirectory = config["BaseDirectory"];

            if (Directory.Exists(baseDirectory) == false)
                Directory.CreateDirectory(baseDirectory);

            initialized = true;
        }

        /// <summary>
        /// 删除一个统一存储文件流。
        /// </summary>
        public static void Delete(string fileName)
        {
            if (initialized == false)
                throw new Exception("统一存储文件流系统未初始化");
            object storageType = null;
            DateTime dtFileCreate;

            using (SqlConnection cn = new SqlConnection())
            {

                cn.ConnectionString = connectionString;
                cn.Open();

                SqlCommand cmd = cn.CreateCommand();
                cmd.CommandType = System.Data.CommandType.Text;
                cmd.CommandText = "SELECT StorageType,CreateTime FROM browis_FileStorage WHERE FileGuid=@FileGUID";
                cmd.Parameters.Add("FileGUID", System.Data.SqlDbType.NVarChar).Value = fileName;
                using (SqlDataReader dr = cmd.ExecuteReader())
                {
                    if (dr.Read())
                    {
                        storageType = dr[0];
                        dtFileCreate = dr.GetDateTime(1);
                    }
                    else
                    {
                        //没有该文件，返回
                        return;
                    }
                }
                //storageType = cmd.ExecuteScalar();
                //if (storageType == null)
                //    return;

                cmd.CommandText = "DELETE FROM browis_FileStorage WHERE FileGUID=@FileGUID";
                cmd.ExecuteNonQuery();

                cn.Close();
            }
            if (storageType != null)
            {
                if ((int)storageType == (int)StorageType.FileSystem)
                    File.Delete(Path.Combine(Path.Combine(baseDirectory, dtFileCreate.ToString("yyyyMMdd")), fileName));
            }
            else
            {
                throw new Exception("无法获得文件的存储类型。");
            }
        }

        /// <summary>
        /// 删除一个统一存储文件流。
        /// </summary>
        public static void Delete(string fileName, SqlTransaction transaction)
        {
            if (initialized == false)
                throw new Exception("统一存储文件流系统未初始化");

            object storageType = null;
            DateTime dtFileCreate;

            SqlCommand cmd = transaction.Connection.CreateCommand();
            cmd.Transaction = transaction;
            cmd.CommandType = System.Data.CommandType.Text;
            cmd.CommandText = "SELECT StorageType,CreateTime FROM browis_FileStorage WHERE FileGUID=@FileGUID";
            cmd.Parameters.Add("FileGUID", System.Data.SqlDbType.NVarChar).Value = fileName;
            using (SqlDataReader dr = cmd.ExecuteReader())
            {
                if (dr.Read())
                {
                    storageType = dr[0];
                    dtFileCreate = dr.GetDateTime(1);
                }
                else
                {
                    //没有该文件，返回
                    return;
                }
            }
            //object storageType = cmd.ExecuteScalar();
            //if (storageType == null)
            //    return;

            cmd.CommandText = "DELETE FROM browis_FileStorage WHERE FileGUID=@FileGUID";
            cmd.ExecuteNonQuery();

            if ((int)storageType == (int)StorageType.FileSystem)
                File.Delete(Path.Combine(Path.Combine(baseDirectory, dtFileCreate.ToString("yyyyMMdd")), fileName));
        }

        protected override void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
                if (fileAccess != FileAccess.Read)
                {
                    using (SqlConnection cn = new SqlConnection())
                    {
                        cn.ConnectionString = connectionString;
                        cn.Open();

                        SqlCommand cmd = cn.CreateCommand();
                        cmd.CommandType = System.Data.CommandType.Text;

                        if (fileStream != null)
                        {
                            cmd.CommandText = "UPDATE browis_FileStorage SET FileSize=@FileSize,UpdateTime=GETDATE() WHERE FileGUID=@FileGUID";
                            cmd.Parameters.Add("FileSize", System.Data.SqlDbType.Decimal).Value = fileStream.Length;
                            cmd.Parameters.Add("FileGUID", System.Data.SqlDbType.NVarChar).Value = fileName;
                        }
                        else
                        {
                            cmd.CommandText = "UPDATE browis_FileStorage SET FileSize=DATALENGTH(FileContent),UpdateTime=GETDATE() WHERE FileGUID=@FileGUID";
                            cmd.Parameters.Add("FileGUID", System.Data.SqlDbType.NVarChar).Value = fileName;
                        }
                        cmd.ExecuteNonQuery();

                        cn.Close();
                    }
                }
                if (fileStream != null)
                {
                    fileStream.Close();
                    fileStream = null;
                }
                this.isOpen = false;
            }
        }
    }
}
